package com.ysz.component.tableshard;

import com.ysz.component.constant.MyBatisConst;
import com.ysz.component.tableshard.strategy.TableShardStrategy;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.binding.MapperMethod;
import org.apache.ibatis.executor.statement.RoutingStatementHandler;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.DefaultReflectorFactory;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.ReflectorFactory;
import org.apache.ibatis.reflection.factory.DefaultObjectFactory;
import org.apache.ibatis.reflection.factory.ObjectFactory;
import org.apache.ibatis.reflection.wrapper.DefaultObjectWrapperFactory;
import org.apache.ibatis.reflection.wrapper.ObjectWrapperFactory;

import java.lang.reflect.Method;
import java.sql.Connection;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Properties;

/**
 * @ClassName MyBatisTableShardInterceptor
 * @Description TODO
 * @Data 14:26
 * @Version 1.0
 * @Author ysz
 */
@Intercepts({
        @Signature(type = StatementHandler.class,
                method = "prepare",
                args = {Connection.class, Integer.class})
})
public class MyBatisTableShardInterceptor implements Interceptor {

    private Boolean enableTableShard;
    private ObjectFactory OBJECT_FACTORY = new DefaultObjectFactory();
    private ObjectWrapperFactory OBJECT_WRAPPER_FACTORY = new DefaultObjectWrapperFactory();
    private ReflectorFactory REFLECTOR_FACTORY = new DefaultReflectorFactory();

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        try {
            if (!enableTableShard) {
                return invocation.proceed();
            }
            RoutingStatementHandler routingStatementHandler = (RoutingStatementHandler) invocation.getTarget();
            //mybatis提供的反射工具类
            MetaObject metaObject = MetaObject.forObject(routingStatementHandler, OBJECT_FACTORY, OBJECT_WRAPPER_FACTORY, REFLECTOR_FACTORY);
            //获取sql
            BoundSql boundSql = (BoundSql) metaObject.getValue("delegate.boundSql");
            MapperMethod.ParamMap parameterObject = (MapperMethod.ParamMap) boundSql.getParameterObject();
            List<Class<?>> methodParamsType = getMethodParamsType(parameterObject);
            String originalSql = boundSql.getSql();
            if (StringUtils.isNotEmpty(originalSql)) {
                //准备替换sql
                MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("delegate.mappedStatement");
                TableShardAnnotation tableShardAnnotation = getTableShardAnnotation(mappedStatement.getId(), methodParamsType);
                if (Objects.nonNull(tableShardAnnotation)) {
                    //获取新的sql
                    String newSql = getNewSql(originalSql, tableShardAnnotation);
                    metaObject.setValue("delegate.boundSql.sql", newSql);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
            return invocation.proceed();
        }
        return invocation.proceed();
    }


    /**
     * 获取mapper方法上的参数class
     *
     * @param parameterObject mybatis封装的参数 其中包含param1  param2..3..4..5..
     * @return
     * @throws
     * @author ysz
     * @date 2022/3/10 15:06
     */
    public List<Class<?>> getMethodParamsType(MapperMethod.ParamMap parameterObject) {
        List<Class<?>> list = new ArrayList<>(parameterObject.size());
        for (int i = 1; i <= parameterObject.size(); i++) {
            try {
                list.add(parameterObject.get("param" + i).getClass());
            } catch (Exception e) {
                //ignore
            }
        }
        return list;
    }


    /**
     * 防止方法重写要精确获取方法对象
     *
     * @param sqlId            sqlId 类的权限定路径+方法名
     * @param methodParamsType 方法的参数列表
     * @return 方法上的分页注解
     * @author ysz
     * @date 2022/3/10 15:18
     */
    public TableShardAnnotation getTableShardAnnotation(String sqlId, List<Class<?>> methodParamsType) {
        String methodName = sqlId.substring(sqlId.lastIndexOf(".") + 1);
        String className = sqlId.substring(0, sqlId.lastIndexOf("."));
        try {
            Method method = Class.forName(className).getMethod(methodName, methodParamsType.toArray(new Class[0]));
            return method.getAnnotation(TableShardAnnotation.class);
        } catch (NoSuchMethodException | ClassNotFoundException e) {
            e.printStackTrace();
        }
        return null;
    }


    /**
     * 获取新的sql
     *
     * @param originalSql          原始sql
     * @param tableShardAnnotation 分表注解
     * @return 新的sql
     * @author ysz
     * @date 2022/3/10 15:19
     */
    public String getNewSql(String originalSql, TableShardAnnotation tableShardAnnotation) {
        try {
            TableShardStrategy tableShardStrategy = tableShardAnnotation.tableShardStrategy().newInstance();
            return tableShardStrategy.tableShard(originalSql, tableShardAnnotation.tableName(), tableShardAnnotation.specifyDay());
        } catch (Exception e) {
            e.printStackTrace();
        }
        return originalSql;
    }


    @Override
    public Object plugin(Object target) {
        // 当目标类是StatementHandler类型时，才包装目标类，否者直接返回目标本身,减少目标被代理的次数
        return (target instanceof RoutingStatementHandler) ? Plugin.wrap(target, this) : target;
    }

    @Override
    public void setProperties(Properties properties) {
        enableTableShard = Boolean.parseBoolean(properties.getProperty(MyBatisConst.sql_table_shard.getKey()));
    }
}
